{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/tchu/anaconda3/lib/python3.6/importlib/_bootstrap.py:219: RuntimeWarning: numpy.dtype size changed, may indicate binary incompatibility. Expected 96, got 88\n",
      "  return f(*args, **kwds)\n",
      "/Users/tchu/anaconda3/lib/python3.6/importlib/_bootstrap.py:219: RuntimeWarning: numpy.dtype size changed, may indicate binary incompatibility. Expected 96, got 88\n",
      "  return f(*args, **kwds)\n"
     ]
    }
   ],
   "source": [
    "%matplotlib inline \n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.ticker as mtick\n",
    "import seaborn as sns\n",
    "sns.set_color_codes()\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import os\n",
    "import xml.etree.cElementTree as ET"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## plot training curves in small grid env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "base_dir = '/Users/tchu/Documents/rl_test/signal_control_results'\n",
    "plot_dir = base_dir + '/plots'\n",
    "if not os.path.exists(plot_dir):\n",
    "    os.mkdir(plot_dir)\n",
    "color_cycle = sns.color_palette()\n",
    "COLORS = {'ma2c': color_cycle[0], 'ia2c': color_cycle[1], 'iqll': color_cycle[2], \n",
    "          'iqld': color_cycle[3], 'greedy':color_cycle[4]}\n",
    "TRAIN_STEP = 1e6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "neighbor_train_reward.csv, neighbor\n",
      "local_train_reward.csv, local\n",
      "global_train_reward.csv, global\n",
      "final performance wrt centralized agent:\n",
      "global: final: -10.21\n",
      "local: final: -9.34\n",
      "neighbor: final: -9.16\n"
     ]
    }
   ],
   "source": [
    "window = 40\n",
    "def plot_train_curve(scenario='small_grid', date='jun06'):\n",
    "    cur_dir = base_dir + ('/eval_%s/%s/train_data' % (date, scenario))\n",
    "    names = ['global', 'local', 'neighbor']\n",
    "    labels = ['Centralized A2C', 'Independent A2C', 'Multi-agent A2C']\n",
    "    if scenario == 'large_grid':\n",
    "        names = names[1:]\n",
    "        labels = labels[1:]\n",
    "    dfs = {}\n",
    "    for file in os.listdir(cur_dir):\n",
    "        name = file.split('_')[0]\n",
    "        print(file + ', ' + name)\n",
    "        if name in names:\n",
    "            df = pd.read_csv(cur_dir + '/' + file)\n",
    "            dfs[name] = df\n",
    "\n",
    "    plt.figure(figsize=(10,8))\n",
    "#     ymin = min([df.value.min() for df in dfs.values()])\n",
    "#     ymax = max([df.value.max() for df in dfs.values()])\n",
    "    ymin = []\n",
    "    ymax = []\n",
    "    xmin = min([df.step.min() for df in dfs.values()])\n",
    "    for i, name in enumerate(names):\n",
    "        df = dfs[name]\n",
    "#         plt.plot(df.Step.values, df.Value.values, color=COLORS[i], linewidth=3, label=labels[i])\n",
    "        x_mean = df.value.rolling(window).mean().values\n",
    "        x_std = df.value.rolling(window).std().values\n",
    "#         x_hi = df.value.rolling(window).max().values\n",
    "        plt.plot(df.step.values, x_mean, color=COLORS[name], linewidth=3, label=labels[i])\n",
    "        ymin.append(np.nanmin(x_mean - x_std))\n",
    "        ymax.append(np.nanmax(x_mean + x_std))\n",
    "        plt.fill_between(df.step.values, x_mean - x_std, x_mean + x_std, facecolor=COLORS[name], edgecolor='none', alpha=0.3)\n",
    "    ymin = min(ymin)\n",
    "    ymax = max(ymax)\n",
    "    plt.xlim([xmin,TRAIN_STEP])\n",
    "    plt.ylim([ymin * 1.05, ymax * 0.95])\n",
    "    plt.xticks(fontsize=15)\n",
    "    plt.yticks(fontsize=15)\n",
    "    # plt.gca().xaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))\n",
    "    plt.xlabel('Training step', fontsize=20)\n",
    "    plt.ylabel('Averaged episode reward', fontsize=20)\n",
    "    plt.legend(loc='lower right', fontsize=20)\n",
    "    plt.tight_layout()\n",
    "    # plt.savefig(plot_dir + '/small_grid_train.png')\n",
    "    plt.savefig(plot_dir + ('/%s_train.pdf' % scenario))\n",
    "    plt.close()\n",
    "\n",
    "    # calculate performance gains\n",
    "    print('final performance wrt centralized agent:')\n",
    "    ys = {}\n",
    "    for name in names:\n",
    "        y = dfs[name].value.values\n",
    "        final = np.mean(y[-window:])\n",
    "        init = np.mean(y[:window])\n",
    "        gain = final - init\n",
    "        print('%s: final: %.2f' % (name, final))\n",
    "plot_train_curve()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## plot training curves in large grid env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "neighbor_train_reward.csv, neighbor\n",
      "local_train_reward.csv, local\n",
      "final performance wrt centralized agent:\n",
      "local: final: -197.56\n",
      "neighbor: final: -142.29\n"
     ]
    }
   ],
   "source": [
    "plot_train_curve(scenario='large_grid')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## plot evaluation curves in small grid env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Greedy policy avg_queue\n",
      "count    3600.000000\n",
      "mean        3.807843\n",
      "std         2.042572\n",
      "min         0.080000\n",
      "25%         1.791667\n",
      "50%         3.273333\n",
      "75%         5.820000\n",
      "max         7.966667\n",
      "Name: avg_queue, dtype: float64\n",
      "(3600,)\n",
      "Greedy policy avg_speed_mps\n",
      "count    3600.000000\n",
      "mean        2.121568\n",
      "std         1.165127\n",
      "min         0.000000\n",
      "25%         1.087307\n",
      "50%         1.897988\n",
      "75%         2.845419\n",
      "max         5.743651\n",
      "Name: avg_speed_mps, dtype: float64\n",
      "(3600,)\n",
      "Greedy policy avg_wait_sec\n",
      "count    3600.000000\n",
      "mean       35.639628\n",
      "std        23.644824\n",
      "min         0.000000\n",
      "25%        14.987172\n",
      "50%        30.097387\n",
      "75%        57.866262\n",
      "max        90.487483\n",
      "Name: avg_wait_sec, dtype: float64\n",
      "(3600,)\n",
      "Greedy policy number_arrived_car\n",
      "count    3600.000000\n",
      "mean        1.115278\n",
      "std         1.112604\n",
      "min         0.000000\n",
      "25%         0.000000\n",
      "50%         1.000000\n",
      "75%         2.000000\n",
      "max         7.000000\n",
      "Name: number_arrived_car, dtype: float64\n",
      "(12,)\n",
      "Greedy policy duration_sec\n",
      "count    4016.000000\n",
      "mean      570.000498\n",
      "std       424.435132\n",
      "min         6.000000\n",
      "25%       252.000000\n",
      "50%       436.000000\n",
      "75%       766.000000\n",
      "max      2983.000000\n",
      "Name: duration_sec, dtype: float64\n",
      "(60,)\n",
      "Greedy policy wait_sec\n",
      "count    4016.000000\n",
      "mean      470.760125\n",
      "std       407.243074\n",
      "min         1.710000\n",
      "25%       168.672500\n",
      "50%       335.590000\n",
      "75%       655.867500\n",
      "max      2860.500000\n",
      "Name: wait_sec, dtype: float64\n",
      "(60,)\n",
      "Greedy policy reward\n",
      "count     720.000000\n",
      "mean    -1207.204861\n",
      "std       693.053838\n",
      "min     -2531.500000\n",
      "25%     -1913.875000\n",
      "50%      -841.000000\n",
      "75%      -617.375000\n",
      "max      -143.500000\n",
      "Name: reward, dtype: float64\n",
      "(720,)\n"
     ]
    },
    {
     "ename": "ValueError",
     "evalue": "could not broadcast input array from shape (720) into shape (360)",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-6-bfdc2d9c4656>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m    158\u001b[0m     plot_combined_series(dfs, names, 'reward', 'control', labels,\n\u001b[1;32m    159\u001b[0m                          'Step reward', scenario + '_reward', reward=True, window=6, agg='mv')\n\u001b[0;32m--> 160\u001b[0;31m \u001b[0mplot_eval_curve\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-6-bfdc2d9c4656>\u001b[0m in \u001b[0;36mplot_eval_curve\u001b[0;34m(scenario, date)\u001b[0m\n\u001b[1;32m    157\u001b[0m                          'Avg trip delay (sec)', scenario + '_tripwait', window=60, agg='mean')\n\u001b[1;32m    158\u001b[0m     plot_combined_series(dfs, names, 'reward', 'control', labels,\n\u001b[0;32m--> 159\u001b[0;31m                          'Step reward', scenario + '_reward', reward=True, window=6, agg='mv')\n\u001b[0m\u001b[1;32m    160\u001b[0m \u001b[0mplot_eval_curve\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-6-bfdc2d9c4656>\u001b[0m in \u001b[0;36mplot_combined_series\u001b[0;34m(dfs, agent_names, col_name, tab_name, agent_labels, y_label, fig_name, window, agg, reward)\u001b[0m\n\u001b[1;32m     89\u001b[0m         \u001b[0mdf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdfs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0maname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtab_name\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     90\u001b[0m         y0, y1 = plot_series(df, col_name, tab_name, agent_labels[i], COLORS[aname], window=window, agg=agg,\n\u001b[0;32m---> 91\u001b[0;31m                              reward=reward)\n\u001b[0m\u001b[1;32m     92\u001b[0m         \u001b[0mymin\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mymin\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     93\u001b[0m         \u001b[0mymax\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mymax\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-6-bfdc2d9c4656>\u001b[0m in \u001b[0;36mplot_series\u001b[0;34m(df, name, tab, label, color, window, agg, reward)\u001b[0m\n\u001b[1;32m     55\u001b[0m                 \u001b[0mcur_x\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfixed_agg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcur_x\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwindow\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0magg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     56\u001b[0m         \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcur_x\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 57\u001b[0;31m         \u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcur_x\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     58\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mnum_episode\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     59\u001b[0m         \u001b[0mx_mean\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mValueError\u001b[0m: could not broadcast input array from shape (720) into shape (360)"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 720x576 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "episode_sec = 3600\n",
    "def fixed_agg(xs, window, agg):\n",
    "    xs = np.reshape(xs, (-1, window))\n",
    "    if agg == 'sum':\n",
    "        return np.sum(xs, axis=1)\n",
    "    elif agg == 'mean':\n",
    "        return np.mean(xs, axis=1)\n",
    "    elif agg == 'median':\n",
    "        return np.median(xs, axis=1)\n",
    "\n",
    "def varied_agg(xs, ts, window, agg):\n",
    "    t_bin = window\n",
    "    x_bins = []\n",
    "    cur_x = []\n",
    "    for x, t in zip(list(xs) + [0], list(ts) + [episode_sec + 1]):\n",
    "        if t <= t_bin:\n",
    "            cur_x.append(x)\n",
    "        else:\n",
    "            if not len(cur_x):\n",
    "                x_bins.append(0)\n",
    "            else:\n",
    "                if agg == 'sum':\n",
    "                    x_stat = np.sum(np.array(cur_x))\n",
    "                elif agg == 'mean':\n",
    "                    x_stat = np.mean(np.array(cur_x))\n",
    "                elif agg == 'median':\n",
    "                    x_stat = np.median(np.array(cur_x))\n",
    "                x_bins.append(x_stat)\n",
    "            t_bin += window\n",
    "            cur_x = [x]\n",
    "    return np.array(x_bins)\n",
    "    \n",
    "def plot_series(df, name, tab, label, color, window=None, agg='sum', reward=False):\n",
    "    episodes = list(df.episode.unique())\n",
    "    num_episode = len(episodes)\n",
    "    num_time = episode_sec\n",
    "    print(label, name)\n",
    "    print(df[name].describe())\n",
    "    if reward:\n",
    "        num_time = 720\n",
    "    if window and (agg != 'mv'):\n",
    "        num_time = num_time // window\n",
    "    x = np.zeros((num_episode, num_time))\n",
    "    for i, episode in enumerate(episodes):\n",
    "        t_col = 'arrival_sec' if  tab == 'trip' else 'time_sec' \n",
    "        cur_df = df[df.episode == episode].sort_values(t_col)\n",
    "        if window and (agg == 'mv'):\n",
    "            cur_x = cur_df[name].rolling(window, min_periods=1).mean().values\n",
    "        else:\n",
    "            cur_x = cur_df[name].values    \n",
    "        if window and (agg != 'mv'):\n",
    "            if tab == 'trip':\n",
    "                cur_x = varied_agg(cur_x, df[df.episode == episode].arrival_sec.values, window, agg)\n",
    "            else:    \n",
    "                cur_x = fixed_agg(cur_x, window, agg)\n",
    "        print(cur_x.shape)\n",
    "        x[i] = cur_x\n",
    "    if num_episode > 1:\n",
    "        x_mean = np.mean(x, axis=0)\n",
    "        x_std = np.std(x, axis=0)\n",
    "    else:\n",
    "        x_mean = x[0]\n",
    "        x_std = np.zeros(num_time)\n",
    "    if (not window) or (agg == 'mv'):\n",
    "        t = np.arange(1, episode_sec + 1)\n",
    "        if reward:\n",
    "            t = np.arange(10, episode_sec + 1, 5)\n",
    "    else:\n",
    "        t = np.arange(window, episode_sec + 1, window)\n",
    "#     if reward:\n",
    "#         print('%s: %.2f' % (label, np.mean(x_mean)))\n",
    "    plt.plot(t, x_mean, color=color, linewidth=3, label=label)\n",
    "    if num_episode > 1:\n",
    "        x_lo = x_mean - x_std\n",
    "        if not reward:\n",
    "            x_lo = np.maximum(x_lo, 0)\n",
    "        x_hi = x_mean + x_std\n",
    "        plt.fill_between(t, x_lo, x_hi, facecolor=color, edgecolor='none', alpha=0.3)\n",
    "        return np.nanmin(x_lo), np.nanmax(x_hi)\n",
    "    else:\n",
    "        return np.nanmin(x_mean), np.nanmax(x_mean)\n",
    "    \n",
    "def plot_combined_series(dfs, agent_names, col_name, tab_name, agent_labels, y_label, fig_name,\n",
    "                         window=None, agg='sum', reward=False):\n",
    "    plt.figure(figsize=(10,8))\n",
    "    ymin = np.inf\n",
    "    ymax = -np.inf\n",
    "    for i, aname in enumerate(agent_names):\n",
    "        df = dfs[aname][tab_name]\n",
    "        y0, y1 = plot_series(df, col_name, tab_name, agent_labels[i], COLORS[aname], window=window, agg=agg,\n",
    "                             reward=reward)\n",
    "        ymin = min(ymin, y0)\n",
    "        ymax = max(ymax, y1)\n",
    "    \n",
    "    plt.xlim([0, episode_sec])\n",
    "    if (col_name == 'average_speed') and ('global' in agent_names):\n",
    "        plt.ylim([0, 6])\n",
    "    elif (col_name == 'wait_sec') and ('global' not in agent_names):\n",
    "        plt.ylim([0, 3500])\n",
    "    else:\n",
    "        plt.ylim([ymin, ymax])\n",
    "    plt.xticks(fontsize=15)\n",
    "    plt.yticks(fontsize=15)\n",
    "    plt.xlabel('Simulation time (sec)', fontsize=20)\n",
    "    plt.ylabel(y_label, fontsize=20)\n",
    "    if (col_name == 'wait_sec'):\n",
    "        plt.legend(loc='upper left', fontsize=20)\n",
    "    else:\n",
    "        plt.legend(loc='best', fontsize=20)\n",
    "    plt.tight_layout()\n",
    "    plt.savefig(plot_dir + ('/%s.pdf' % fig_name))\n",
    "    plt.close()\n",
    "    \n",
    "def sum_reward(x):\n",
    "    x = [float(i) for i in x.split(',')]\n",
    "    return np.sum(x)\n",
    "\n",
    "def plot_eval_curve(scenario='large_grid', date='oct03'):\n",
    "    cur_dir = base_dir + ('/eval_%s/%s/eva_data' % (date, scenario))\n",
    "    names = ['greedy']\n",
    "    labels = ['Greedy policy']\n",
    "    dfs = {}\n",
    "    for file in os.listdir(cur_dir):\n",
    "        if not file.endswith('.csv'):\n",
    "            continue\n",
    "        if not file.startswith(scenario):\n",
    "            continue\n",
    "        name = file.split('_')[2]\n",
    "        measure = file.split('_')[3].split('.')[0]\n",
    "        if name in names:\n",
    "            df = pd.read_csv(cur_dir + '/' + file)\n",
    "#             if measure == 'traffic':\n",
    "#                 df['ratio_stopped_car'] = df.number_stopped_car / df.number_total_car * 100\n",
    "#             if measure == 'control':\n",
    "#                 df['global_reward'] = df.reward.apply(sum_reward)\n",
    "            if name not in dfs:\n",
    "                dfs[name] = {}\n",
    "            dfs[name][measure] = df\n",
    "    \n",
    "    # plot avg queue\n",
    "    plot_combined_series(dfs, names, 'avg_queue', 'traffic', labels,\n",
    "                         'Average queue length (veh)', scenario + '_queue', window=60, agg='mv')\n",
    "    # plot avg speed\n",
    "    plot_combined_series(dfs, names, 'avg_speed_mps', 'traffic', labels,\n",
    "                         'Average car speed (m/s)', scenario + '_speed', window=60, agg='mv')\n",
    "    # plot avg waiting time\n",
    "    plot_combined_series(dfs, names, 'avg_wait_sec', 'traffic', labels,\n",
    "                         'Average waiting time (sec)', scenario + '_wait', window=60, agg='mv')\n",
    "    # plot trip completion\n",
    "    plot_combined_series(dfs, names, 'number_arrived_car', 'traffic', labels,\n",
    "                         'Trip completion rate (veh/5 min)', scenario + '_tripcomp', window=300, agg='sum')\n",
    "    # plot trip time\n",
    "    plot_combined_series(dfs, names, 'duration_sec', 'trip', labels,\n",
    "                         'Avg trip time (sec)', scenario + '_triptime', window=60, agg='mean')\n",
    "    # plot trip waiting time\n",
    "    plot_combined_series(dfs, names, 'wait_sec', 'trip', labels,\n",
    "                         'Avg trip delay (sec)', scenario + '_tripwait', window=60, agg='mean')\n",
    "    plot_combined_series(dfs, names, 'reward', 'control', labels,\n",
    "                         'Step reward', scenario + '_reward', reward=True, window=6, agg='mv')\n",
    "plot_eval_curve()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# plot evaluation curves in large grid env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Independent A2C: -268.44\n",
      "Multi-agent A2C: -124.13\n",
      "Greedy policy: -207.65\n"
     ]
    }
   ],
   "source": [
    "plot_eval_curve(scenario='large_grid')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
